// 特质对象限制，不是所有特质都能被用做特对象，只有【安全对象】才能被使用，所有方法都要满足安全规则，才被认为安全
trait Safe {
    // 1.不符合Self: Sized约束
    fn test1() -> Self; // 显式: Self: Sized

    // 2.返回值不能是Self（类型本身），Self大小未知（1和2，说的是一回事儿，本质在说大小未知不安全，不符合安全对象要求）
    fn test2(&self) -> Self;

    // 3.不能有泛型参数
    fn test3<T>(&self, value: T);
}

trait Some {
    fn thing(&self) -> String;
}

struct Empty;

impl Some for Empty {
    fn thing(&self) -> String {
        String::from("something")
    }
}

// 静态分配(特质约束)，在编译时就将所有类型罗列开，性能是很好的没耗损，但增大了编译后二进制文件的体积
fn call_impl(e: &impl Some) {
    println!("trait object &impl {}", e.thing());
}

fn call_stract(e: impl Some) {
    println!("trait object stract {}", e.thing());
}

// 动态分配（特质对象），运行时多态性，导致在运行时需要执行查虚表的方法（对内存有开销），所以有一定性能耗损
fn call_dyn(e: &dyn Some) {
    println!("trait object &dyn {}", e.thing());
}

// 为甚，不能在类型后直接填入dyn Some，因为【特质对象】映射的类型（包括不限于结构体）大小在编译时无法确定有多大，但编译时又要将函数的参数分配到栈上，所以这里只能用引用（引用长度是确定的），或Box智能指针打包(包装)类型（将这个不确定因素放到堆上去）
fn call_box(e: Box<dyn Some>) {
    println!("trait object box {}", e.thing());
}

// 以下是对特质对象的应用
trait Sale {
    fn amount(&self) -> f64;
}

struct Tea(f64);
impl Sale for Tea {
    fn amount(&self) -> f64 {
        self.0 * 0.8
    }
}

struct Beer(f64);
impl Sale for Beer {
    fn amount(&self) -> f64 {
        if self.0 > 30.0 {
            self.0 - 10.0
        } else {
            self.0
        }
    }
}

struct Wine(f64);
impl Sale for Wine {
    fn amount(&self) -> f64 {
        self.0 * 0.9
    }
}

fn calculate(vec: Vec<Box<dyn Sale>>) -> f64 {
    vec.iter().map(|x: &Box<dyn Sale>| x.amount()).sum()
}

fn main() {
    let e = Empty;
    call_impl(&e);
    call_stract(e);

    let e = Empty;
    call_dyn(&e);
    // 通过box将结构体，从栈转移到堆上，避免栈上占用大量内存
    call_box(Box::new(e));

    let tea = Tea(30.0);
    let beer = Beer(50.0);
    let wine = Wine(88.0);

    let total = calculate(vec![Box::new(tea), Box::new(beer), Box::new(wine)]);

    println!("total: {}", total)
}
